%{
  package interpreter;
  import java.io.*;
  import matrix.*;
  import variables.*;
%}

%token <dval> NUMBER
%token <sval> ID
%token COLON
%token SEMICOLON
%token LSBRACKET
%token RSBRACKET
%token DOTADD
%token DOTMINUS
%token DOTMULT
%token DOTDIV
%token DOTEXP
%token NL 

%type <obj> matrix
%type <obj> matrixExp
%type <dval> numExp
%start input

%left "-" "+" DOTADD DOTMINUS
%left "*" "/" DOTMULT DOTDIV
%right "^"         /* matrixExponentiation        */
%right "'"         /* matrix transposition      */
%nonassoc NEG      /* negation--unary minus */

%%

input: /* empty string */
      | input line
       ;
      
line:
     NL
       { }
       |
     SEMICOLON NL
       { }
       |
     ID SEMICOLON NL
       { }
       |
     matrixExp SEMICOLON NL
       { 
             //supress output - semicolon... do nothing :)
       }
       |
     matrixExp NL
       { 
             addToWriter(("ans = " + $1.toString() + "\n")); 
       }
       |
     numExp SEMICOLON NL
       { 
             //supress output - semicolon... do nothing :)
       }
       |
     numExp NL
       {
             addToWriter(("ans = " + $1 + "\n")); 
       }
       |
     ID NL
       {
             Object obj = null;
             try {
                obj = variables.get($1.toUpperCase());
             } catch (Exception e) {
                addToWriter(e.getMessage()); //there is no such variable
                return -1;
             }
             addToWriter(($1.toUpperCase()+" = " + obj.toString() + "\n"));
       }
       |
    ID '=' matrixExp SEMICOLON NL
       {
            //supress output - semicolon... but save as a variable
            variables.add((Matrix)$3, $1.toUpperCase());
       }
       |
    ID '=' matrixExp NL
       {
            //display results, and save as a variable
            variables.add((Matrix)$3, $1.toUpperCase());
            addToWriter(($1.toUpperCase()+" = " + $3.toString() + "\n"));             
       }
       |
    ID '=' numExp NL
       {
            //no support for numExp variables... don't have idea how to implement this feature
            addToWriter("Sorry, but there no support for numeric variables...\n");
       }
       |
    ID '=' numExp SEMICOLON NL
       {
            //no support for numExp variables... don't have idea how to implement this feature
            addToWriter("Sorry, but there no support for numeric variables...\n");
       }
       ;
      
matrixExp:
    matrix
       {
            $$ = $1;
       }
       |
      ID
       {
            Object obj = null;
            try {
                obj = variables.get($1.toUpperCase());
            } catch (Exception e) {
                addToWriter(e.getMessage()); //there is no such variable
                return -1;
            }
            $$=(Matrix)obj;
       }
       |
   '(' matrixExp ')'
       {
            $$ = $2;
       }   
       |
    "|" matrixExp "|"
       {
            tempMatrix=(Matrix)$2;
            $$ = new Double(tempMatrix.determinant());
       }
       |
    matrixExp "'"
       {
            tempMatrix=(Matrix)$1;
            tempMatrix=tempMatrix.transpose();
            $$ = (Matrix)tempMatrix;
       }
       |
    matrixExp "^" numExp
       {
            tempMatrix=(Matrix)$1;
            double exp = $3;
            if(exp < 0) {
                try {
                    tempMatrix=tempMatrix.inverse();
                }  catch (Exception e) {
                        addToWriter("Error while inversing matrix: "+e.getMessage()+"!\n");
                        return -1;
                }
                Matrix tmp = new Matrix(tempMatrix);
                try {
                    for (int i=0;i<(int)(exp*-1-1);i++) {
                        tempMatrix=tempMatrix.mult(tmp);
                    }
                } catch (MatrixDimensionError e) {
                    addToWriter("Matrix dimension error 1st ("+tempMatrix.rows()+", "+tempMatrix.cols()+"), 2d ("+tempMatrix.rows()+", "+tempMatrix.cols()+")!\n");
                    return -1;
                }
            }
            else if (exp == 0) {
                int i = tempMatrix.rows();
                tempMatrix=new Matrix(i);
            }
            else {
                tempMatrix=(Matrix)$1;
                Matrix tmp = new Matrix(tempMatrix);
                try {
                    for (int i=0;i<(int)exp-1;i++) {
                        tempMatrix=tempMatrix.mult(tmp);
                    }
                } catch (MatrixDimensionError e) {
                    addToWriter("Matrix dimension error 1st ("+tempMatrix.rows()+", "+tempMatrix.cols()+"), 2d ("+tempMatrix.rows()+", "+tempMatrix.cols()+")!\n");
                    return -1;
                }
            }
            $$ = (Matrix)tempMatrix;

       }
       |
    matrixExp DOTEXP numExp
       {
            tempMatrix=(Matrix)$1;
            tempMatrix.selfExp($3);
            $$=tempMatrix;
       }
       |
    matrixExp '+' matrixExp
       {
           try {
                tempMatrix=(Matrix)$1;
                tempMatrix=tempMatrix.add((Matrix)$3);
                $$ = (Matrix)tempMatrix;
           }
           catch (MatrixDimensionError e) {
                Matrix tempMatrix1 = new Matrix((Matrix)$1);
                Matrix tempMatrix2 = new Matrix((Matrix)$3);
                addToWriter("Matrix dimension error 1st ("+tempMatrix1.rows()+", "+tempMatrix1.cols()+"), 2d ("+tempMatrix2.rows()+", "+tempMatrix2.cols()+")!\n");
                return -1;
           }
       }
       |
    matrixExp DOTADD numExp
       {
           tempMatrix=(Matrix)$1;
           tempMatrix.selfAdd((double)$3);
           $$ = (Matrix)tempMatrix;
       }
       |
    numExp DOTADD matrixExp 
       {
           tempMatrix=(Matrix)$3;
           tempMatrix.selfAdd((double)$1);
           $$ = (Matrix)tempMatrix;
       }
       |
    matrixExp '+' numExp
       {
           tempMatrix=(Matrix)$1;
           tempMatrix.selfAdd((double)$3);
           $$ = (Matrix)tempMatrix;
       }
       |
    numExp '+' matrixExp
       {
           tempMatrix=(Matrix)$3;
           tempMatrix.selfAdd((double)$1);
           $$ = (Matrix)tempMatrix;
       }
    
       |
    matrixExp '-' matrixExp
       {
           try {
                tempMatrix=(Matrix)$1;
                tempMatrix=tempMatrix.add((Matrix)$3);
                $$ = (Matrix)tempMatrix;
           }
           catch (MatrixDimensionError e) {
                Matrix tempMatrix1 = new Matrix((Matrix)$1);
                Matrix tempMatrix2 = new Matrix((Matrix)$3);
                addToWriter("Matrix dimension error 1st ("+tempMatrix1.rows()+", "+tempMatrix1.cols()+"), 2d ("+tempMatrix2.rows()+", "+tempMatrix2.cols()+")!\n");
                return -1;
           }
       }
       |
    matrixExp DOTMINUS numExp
       {
           tempMatrix=(Matrix)$1;
           tempMatrix.selfAdd((double)-1*$3);
           $$ = (Matrix)tempMatrix;
       }
       |
   numExp DOTMINUS matrixExp 
       {
           tempMatrix=(Matrix)$3;
           tempMatrix.selfAdd((double)-1*$1);
           $$ = (Matrix)tempMatrix;
       }
       |
    matrixExp '-' numExp
       {
           tempMatrix=(Matrix)$1;
           tempMatrix.selfAdd((double)-1*$3);
           $$ = (Matrix)tempMatrix;
       }
       |
    numExp '-' matrixExp 
       {
           tempMatrix=(Matrix)$3;
           tempMatrix.selfAdd((double)-1*$1);
           $$ = (Matrix)tempMatrix;
       }
       |
    matrixExp '*' matrixExp
       {
            try {
                tempMatrix=(Matrix)$1;
                tempMatrix=tempMatrix.mult((Matrix)$3);
                $$ = (Matrix)tempMatrix;
            } catch (MatrixDimensionError e) {
                Matrix tempMatrix1 = new Matrix((Matrix)$1);
                Matrix tempMatrix2 = new Matrix((Matrix)$3);
                addToWriter("Matrix dimension error 1st ("+tempMatrix1.rows()+", "+tempMatrix1.cols()+"), 2d ("+tempMatrix2.rows()+", "+tempMatrix2.cols()+")!\n");
                return -1;
            }
       }
       |
    matrixExp DOTMULT numExp
       {
           tempMatrix=(Matrix)$1;
           tempMatrix.selfMult((double)$3);
           $$ = (Matrix)tempMatrix;
       }
       |
     numExp DOTMULT matrixExp 
       {
           tempMatrix=(Matrix)$3;
           tempMatrix.selfMult((double)$1);
           $$ = (Matrix)tempMatrix;
       }
       |
    matrixExp '*' numExp
       {
           tempMatrix=(Matrix)$1;
           tempMatrix.selfMult((double)$3);
           $$ = (Matrix)tempMatrix;
       }
       |
     numExp '*' matrixExp 
       {
           tempMatrix=(Matrix)$3;
           tempMatrix.selfMult((double)$1);
           $$ = (Matrix)tempMatrix;
       }
       |
    matrixExp '/' matrixExp
       {
            try {
                Matrix tempMatrix1=(Matrix)$3;
                tempMatrix=(Matrix)$1;
                try {
                    tempMatrix=tempMatrix.mult(tempMatrix1.inverse());
                    $$ = (Matrix)tempMatrix;
                } catch (Exception e) {
                    addToWriter("Error while inversing matrix: "+e.getMessage()+"!\n");
                    return -1;
                }
            } catch (MatrixDimensionError e) {
                Matrix tempMatrix1 = new Matrix((Matrix)$1);
                Matrix tempMatrix2 = new Matrix((Matrix)$3);
                addToWriter("Matrix dimension error, 1st matrix ("+tempMatrix1.rows()+", "+tempMatrix1.cols()+"), 2d  matrix ("+tempMatrix2.rows()+", "+tempMatrix2.cols()+")!\n");
                return -1;
            }
       }
       |
    matrixExp DOTDIV numExp
       {
           tempMatrix=(Matrix)$1;
           tempMatrix.selfDiv((double)$3);
           $$ = (Matrix)tempMatrix;
       }
       |
    numExp DOTDIV matrixExp 
       {
           tempMatrix=(Matrix)$3;
           tempMatrix.selfDiv((double)$1);
           $$ = (Matrix)tempMatrix;
       }
       |
    matrixExp '/' numExp
       {
           tempMatrix=(Matrix)$1;
           tempMatrix.selfDiv((double)$3);
           $$ = (Matrix)tempMatrix;
       }
       |
    numExp '/' matrixExp
       {
           tempMatrix=(Matrix)$3;
           tempMatrix.selfDiv((double)$1);
           $$ = (Matrix)tempMatrix;
       }
       ;

matrix:
       {
           tempMatrix=new Matrix(1,1);
       }
    LSBRACKET rows RSBRACKET
       {
           c_col=0;
           c_row=0;
           $$ = (Matrix)tempMatrix;
       }
       ;
       
rows:
    row
    {
        c_col=0;
        c_row++;
    }
    | rows SEMICOLON row
    {
        c_col=0;
        c_row++;
    }
    ;

row:
    numExp COLON numExp
        {
            for (double i=$1;i<$3;i++) {
                tempMatrix.setCell(c_row, c_col, i);
                c_col++;
            }
            c_col++;
        }
        |
    numExp COLON numExp COLON numExp
        {
            for (double i=$1;i<$5;i+=$3) {
                tempMatrix.setCell(c_row, c_col, i);
                c_col++;
            }
            c_col++;
        }
        |
    numExp
        {
            tempMatrix.setCell(c_row, c_col, $1);
            c_col++;
        }
        |
   row numExp
        {
            tempMatrix.setCell(c_row, c_col, $2);
            c_col++;
        }
    ;

numExp: NUMBER                    {  $$=$1; }
       | numExp '+' numExp        { $$ = $1 + $3; }
       | numExp "-" numExp        { $$ = $1 - $3; }
       | "+" numExp %prec NEG     { $$ = $2; }
       | "-" numExp %prec NEG     { $$ = -$2; }
       | numExp '*' numExp        { $$ = $1 * $3; }
       | numExp '/' numExp        { $$ = $1 / $3; }
       | numExp '^' numExp        { $$ = myjava.Math.pow($1, $3); }
       | '(' numExp ')'           { $$ = $2; }
     ;

%%
  private int c_row;
  private int c_col;
  private int c_matrix;
  private Matrix tempMatrix;
  private Yylex lexer;
  private ByteArrayOutputStream byteArrayWriter;
  private Writer writer;
  private Variables variables;
  
  private int yylex () {
    int yyl_return = -1;
    try {
      yylval = new ParserVal(0);
      yyl_return = lexer.yylex();
    }
    catch (IOException e) {
        System.out.println("IO error :" + e);
    }
    return yyl_return;
  }

  public void addToWriter (String s) {
    if(this.yydebug!=true) {
        try {
            writer.write(s, 0, s.length());
        } catch (IOException e) {
            System.out.println("IO error :" + e);
        }
     } else {
        System.out.print(s);
     }
  }
  
  public void yyerror (String error) {
    String s=null;
    if (this.yychar <= YYMAXTOKEN) //check index bounds
        s = this.yyname[this.yychar];    //now get it
    if (s==null)
        s = "illegal-symbol";
    if(error.equalsIgnoreCase("stack underflow. aborting..."))
        addToWriter("Parser error at line "+(lexer.getLineNumber()+1)+ ", char "+(lexer.getCharNumber()+1)+ ".\nUnexpected token: "+s+" ("+lexer.getLastToken()+")\n");
  }

  public Parser(Reader r) {
    lexer = new Yylex(r, this);
    c_row=0;
    c_col=0;
    c_matrix=0;
    tempMatrix = new Matrix (1, 1);
    byteArrayWriter=new ByteArrayOutputStream();
    writer=new OutputStreamWriter(byteArrayWriter);
    variables = new Variables();
    //this.yydebug=true;
  }

  
  public void parse() {
    this.yyparse();
  }
  
  public String getOutput() {
    return byteArrayWriter.toString();
  }
  
  public Variables getVariables() {
    return this.variables;
  }
  
  /*public static void main(String args[]) throws IOException {
    System.out.println("matlabMe cmd line version");

      Parser yyparser;

      System.out.println("[Quit with CTRL-D]");
      System.out.print("Expression: ");
      yyparser = new Parser(new InputStreamReader(System.in));
      yyparser.yyparse();
   
  }*/